// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of Ubiquity formsPlayer
//
// Ubiquity formsPlayer is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// Ubiquity formsPlayer is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with Ubiquity formsPlayer If not, see <http://www.gnu.org/licenses/>.
//	
//
//   Purpose: Implementation of CImplAction and CImplActionEventListener.
//   $Id$


#include "stdafx.h"
#include <new>
#include "XForms.h"
#include "XFormsApp.h"
#include "XFormsXFormsElement.h"

#include "Parse.h"
#include "Actions.h"
#include "formcontrol.h"
#include "output.h"
#include "XFormsElementAction.h"
#include "eventmsg.h"
#include "Model.h"
#include "nodesetBinding.h"

extern CXFormsApp theApp;

void CImplAction::notifyDelete()
{

}


HRESULT CImplAction::GetXPathContext(IXMLDOMNode **ppNode)
{
	//returns a node for evaluation of references in context.
	HRESULT hr = S_FALSE;


	if(m_pContextSet)
	{
		//If the context set has been defined by iterate, use that.
		m_pContextSet->snapshotItem(m_ulCurrentIteration,ppNode);
		hr = S_OK;
	}
	else
	{
		CImplAction * pParentAsAction = dynamic_cast<CImplAction*>(m_pParentImpl);
		if(pParentAsAction)
		{
			if(m_bHasResolvedOwnModel && pParentAsAction->m_pModel != m_pModel)
			{
				hr = m_pModel->GetDefaultEvaluationContext(ppNode);		
			}	
			else
			{
				hr = pParentAsAction->GetXPathContext(ppNode);
				setModel(pParentAsAction->m_pModel);
			}
		}
	}
	
	if(hr != S_OK)
		//i.e. nothing is fetched from iterate set
	{
		if(m_pParent)
		{
			CImplSingleNodeBinding * pContextEvaluator  = 0;
			//nullify *ppNode to prevent false success after getInstanceNode
			*ppNode = 0;

			//Get the immediately enclosing binding element node.
			
			CImpl * pEnclosingNode = m_pParent;
			while(pEnclosingNode && !pContextEvaluator)
			{
				pContextEvaluator = dynamic_cast<CImplSingleNodeBinding*>(pEnclosingNode);
				if(pContextEvaluator && !pContextEvaluator->m_bisBound)
				{
					pContextEvaluator = 0;
				}
				
				if(pEnclosingNode->m_pParent)
				{
					pEnclosingNode = pEnclosingNode->m_pParent;
				}
				else
				{
					pEnclosingNode = pEnclosingNode->m_pParentImpl;
				}
			}
			
			if(pContextEvaluator && (pContextEvaluator->m_pModel == m_pModel || !m_bHasResolvedOwnModel))
			{
				pContextEvaluator->getInstanceNode(ppNode);
				if(*ppNode)
				{
					//If a context node has been found, 
					//Do no more.
					hr = S_OK;
				}
				else
				{
					CImplOutput * pOutput = dynamic_cast<CImplOutput*>(pContextEvaluator);
					if(pOutput)// && pOutput->m_sValue.Length > 0)
					{
						//If pContextEvaluator was successfully returned, 
						//	it may not be bound to a node (eg. <output value="fn()" />)
						//	In that case, get its context.
						hr = pContextEvaluator->GetEvaluationContext(ppNode);
					}
					else
					{
						*ppNode = 0;
						hr = S_FALSE;
					}
				}
				if(!m_bHasResolvedOwnModel)
					setModel(pContextEvaluator->m_pModel);

			}
			else
			{
				//if pContextEvaluator was not successfully returned, 
				//or its model is incompatible with this model,
				//	context is NULL (which later evaluates to the default context)

				hr = m_pModel->GetDefaultEvaluationContext(ppNode);
			}
		}
		else if(m_pParentImpl)
		{
			//TODO: This branch should be implemented without dynamic cast.
			//		in case we find a way of working without RTTI.
			//		I suggest that m_pParentImpl or equivalent should
			//		be a CImplSingleNodeBinding
			//		
			
			{
				CImpl * pEnclosingNode = m_pParentImpl;
				CImplModel * pEnclosingModel = 0;

				CImplSingleNodeBinding * pContextEvaluator = 0;
				while(pEnclosingNode && !pContextEvaluator && !pEnclosingModel)
				{
					pContextEvaluator = dynamic_cast<CImplSingleNodeBinding*>(pEnclosingNode);
					if(pContextEvaluator && !pContextEvaluator->m_bisBound)
					{
						pContextEvaluator = 0;
					}
					else if(dynamic_cast<CImplNodesetBinding*>(pEnclosingNode))
					{
						//nodeset binding can't be context, and acts as a barrier to it.
						pContextEvaluator = 0;
						break;
					}
					pEnclosingModel = dynamic_cast<CImplModel*>(pEnclosingNode);
					if(pEnclosingNode->m_pParent)
					{
						pEnclosingNode = pEnclosingNode->m_pParent;
					}
					else
					{
						pEnclosingNode = pEnclosingNode->m_pParentImpl;
					}
				}

				if(pContextEvaluator && (!m_bHasResolvedOwnModel || m_pModel == pContextEvaluator->m_pModel))
				{
					pContextEvaluator->getInstanceNode(ppNode);
					if(!m_bHasResolvedOwnModel)
						setModel(pContextEvaluator->m_pModel);
				}
				else
				{
					CImplModel * pModel = m_pModel;
					if(!pModel)
						pModel = pEnclosingModel;
					if(!pModel)
						m_pXForms->getModel(0,pModel);
					if(!m_pModel)
						setModel(pModel);
					pModel->GetDefaultEvaluationContext(ppNode);
				}
				hr = S_OK;


			}
		}
		else if(m_pModel)
		{
			//	otherwise, use the default evaluation context.
			hr = m_pModel->GetDefaultEvaluationContext(ppNode);
		}
		else
		{
			*ppNode = 0;
			hr = E_FAIL;
		}
	}

	return hr;
}

CIdResolver * CImplAction::GetIdResolver(void) const
{
	if(m_pParent)
		return m_pParent->GetIdResolver();
	else
		return __super::GetIdResolver();
}


CImplAction::CImplAction()
	:m_pControl(NULL)
	,m_pEventListener(0)
	,m_bHasResolvedOwnModel(false)
	,m_ulCurrentIteration(0)
{
}

//CImplAction::CImplAction(CComObject<CXFormsXFormsElement> * pXForms):
CImplAction::CImplAction(CXFormsXFormsElement * pXForms):
	CImpl(pXForms)
	,m_pControl(NULL)
	,m_pEventListener(0)
	,m_bHasResolvedOwnModel(false)
	,m_ulCurrentIteration(0)
{}

CImplAction::CImplAction(CXFormsElementAction * pXFormsElement):
	CImpl(pXFormsElement)
	,m_pControl(NULL)
	,m_pEventListener(0)
	,m_bHasResolvedOwnModel(false)
	,m_ulCurrentIteration(0)
{}


CImplAction::~CImplAction() 
{
	m_pXForms = 0;
	EnterDeletionPostponementState();
	if(m_pTarget && m_pEventListener)
		m_pTarget->removeEventListener(m_sEvent.m_str,m_pEventListener,FALSE);
	if(m_pEventListener)
	{
		m_pEventListener->m_pAction = 0;
		m_pEventListener->Release();
		m_pEventListener = 0;
	}
	if(m_pXFormsElement)
		m_pXFormsElement->formControlUnInitialize();
}


HRESULT CImplAction::Invoke()
{
	//Tell the form that deferred update is running.
	//TODO:
	//Rather than be a flag, this should probably
	//	be passed into the Invoke()s, otherwise 
	//	multithreading willl be impossible.
	//	However, multithreading should probably
	//	be impossible, or handled by a deferred update 
	//	mechanism using thread scheduling.
	HRESULT hr = E_FAIL;
	if(m_pXForms) 
	{
		EnterDeletionPostponementState();
		//must try/catch in order to ensure deletion postponement is left.
		try
		{
			m_pXForms->IncreaseDeferredUpdateCnt();
			try
			{
				//hr = PerformAction();
				hr = InvokeIteratively();
			}
			catch(...)
			{
			}
			//It is plausible thatdelete will have been requested during the invocation.
			//	Check m_pXForms again.
			if(m_pXForms)
				m_pXForms->DecreaseDeferredUpdateCnt();
		}
		catch(...)
		{
		}
		ExitDeletionPostponementState();
	} 
	return hr;
}

HRESULT CImplAction::InvokeIteratively()
{
	HRESULT hr = S_FALSE;
	unsigned long lIterations;
	PrepareIterationNodeset(&lIterations);
	for(m_ulCurrentIteration = 0;m_ulCurrentIteration < lIterations; ++m_ulCurrentIteration)
	{
#ifdef INVOKE_ITERATIVELY_WITH_LOOP
		MSG msg;
		// force msg-q to be created just in case, NOP otherwise
		PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE| PM_NOYIELD);
		// msg to myself to do work
		DWORD dwThread = GetCurrentThreadId();
		PostThreadMessage(dwThread, WM_USER+1, 0, 0);
		// msg-pump
		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) 
		{
			// this was my message -- time to do my work
			if (msg.hwnd == NULL && msg.message == WM_USER+1)
			{
#endif
				hr = InvokeConditionally();
#ifdef INVOKE_ITERATIVELY_WITH_LOOP
			}
			else
			{
              TranslateMessage(&msg);
              DispatchMessage(&msg);
			}
		}
#endif
	}
	FlushIterationNodeset();	
	return hr;
}

HRESULT CImplAction::InvokeConditionally()
{
	HRESULT hr = S_FALSE;
	if(TestIfCondition())
	{
		if(!!m_sWhile)
		{
			//If a while condition exists, loop on it
			while(TestWhileCondition())
			{
				hr = PerformAction();
			}
		}
		else
		{
				hr = PerformAction();
		}
	}

	return hr;
}

bool CImplAction::TestWhileCondition()
{
	bool bReturnValue = false;
	CComPtr<IXMLDOMNode> pContext;
	GetXPathContext(&pContext);
	if(pContext)
	{
		CImplModel * pModel = 0;
		if(m_pModel)
			pModel = m_pModel;
		else
			m_pXForms->getModel(0,pModel);

		bReturnValue = pModel->getInstanceBoolean(pContext,m_sWhile);
	}
	return bReturnValue;
}

bool CImplAction::TestIfCondition()
{
	bool bReturnValue;
	if(!!m_sIf)
	{
		CComPtr<IXMLDOMNode> pContext;
		GetXPathContext(&pContext);

		if(pContext)
		{
			CImplModel * pModel = 0;
			if(m_pModel)
				pModel = m_pModel;
			else
				m_pXForms->getModel(0,pModel);
			bReturnValue = pModel->getInstanceBoolean(pContext,m_sIf);
		}
		else
		{
			bReturnValue = false;
			ASSERT(0);
		}

	}
	else
	{
		bReturnValue = true;
	}

	return bReturnValue;
}

HRESULT CImplAction::PerformNextChildAction(CMap<CImpl *,CImpl *, CImpl *,CImpl *>::CPair * pPair)
{
	HRESULT hr = S_FALSE;
	CImplAction * pChildAction = dynamic_cast<CImplAction*>(pPair->value);
	if(pChildAction)
	{
		if(m_pXForms && m_pXForms->modalMessageActive())
		{
			//If there is a modal message active, store m_pAction
			//	to execute after it has returned.
			hr = m_pXForms->AddPostModalAction(pChildAction);
		}
		else
		{
			//otherwise, invoke straight away.
			hr = pChildAction->Invoke();
		}
	}
	CMap<CImpl *,CImpl *, CImpl *,CImpl *>::CPair * pNextPair = m_Children.PGetNextAssoc(pPair);
	if(pNextPair)
		hr = PerformNextChildAction(pNextPair);
	return hr;
}

HRESULT CImplAction::AddChild(CImpl * pChild)
{
	m_ActionList.push_back(dynamic_cast<CImplAction*>(pChild));
	return __super::AddChild(pChild);
}

HRESULT CImplAction::PerformAction(void)
{
	bool bStrawkcab = IsWithinRepeat(this);
	HRESULT hr = S_FALSE;
	
	if(bStrawkcab)
	{
		CImplActionList::reverse_iterator i = m_ActionList.rbegin();
		while(i !=m_ActionList.rend() && !IsAwaitingDeletion())
		{
			CImplAction * pChildAction = (CImplAction*) *i;
			if(pChildAction)
			{
				if(m_pXForms && m_pXForms->modalMessageActive())
				{
					//If there is a modal message active, store m_pAction
					//	to execute after it has returned.
					hr = m_pXForms->AddPostModalAction(pChildAction);
				}
				else
				{
					//otherwise, invoke straight away.
					hr = pChildAction->Invoke();
				}
			}
			++i;
		}
	}
	else
	{
		CImplActionList::iterator i = m_ActionList.begin();
		while(i !=m_ActionList.end())
		{
			CImplAction * pChildAction = (CImplAction*) *i;
			if(pChildAction)
			{
				if(m_pXForms && m_pXForms->modalMessageActive())
				{
					//If there is a modal message active, store m_pAction
					//	to execute after it has returned.
					hr = m_pXForms->AddPostModalAction(pChildAction);
				}
				else
				{
					//otherwise, invoke straight away.
					hr = pChildAction->Invoke();
				}
			}
			++i;
		}
	}
	return hr;
}

STDMETHODIMP CImplActionEventListener::handleEvent(IDOMEvent * evt)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	ASSERT(m_pAction);
	
	HRESULT hr = E_FAIL;
#ifndef _DEBUG
	try {
#endif
		CXFormsXFormsElement * pXForms = m_pAction->getXFormsElement();
		
		if(pXForms && pXForms->modalMessageActive())
		{
			//If there is a modal message active, store m_pAction
			//	to execute after it has returned.
			hr = pXForms->AddPostModalAction(m_pAction);
		}
		else
		{
			//otherwise, invoke straight away.
			hr = m_pAction->Invoke();
		}
#ifndef _DEBUG
	}
	catch(std::bad_alloc) {
		if(SUCCEEDED(theApp.FormsManager().RegisterEventLog()))
			theApp.FormsManager().WriteEvent(EVENTLOG_ERROR_TYPE, MSG_MEMEX);
		ASSERT(0);
	}
	catch(...) {
		if(SUCCEEDED(theApp.FormsManager().RegisterEventLog()))
			theApp.FormsManager().WriteEvent(EVENTLOG_ERROR_TYPE, MSG_TOPEX);
		ASSERT(0);
	}
#endif
	return hr;
}

void CImplActionEventListener::FinalRelease()
{
	try
	{
		if(m_pTarget)
		{
			CComBSTR s;
			if(m_pAction)
			{
				m_pAction->getEvent(&s);
				m_pTarget->removeEventListener(s,this,FALSE);	
			}
		}
	}
	catch(...)
	{
		ASSERT(0);
	}
	//TODO: this is unsafe, we don't strictly know if 
	//	action was created with new.
	CDelayedDelete * pDD = dynamic_cast<CDelayedDelete *>(m_pAction);
	if(pDD)
		pDD->RequestDeleteAtEarliestConvenience();
	else
		delete m_pAction;
	m_pAction = 0;
}

void CImplActionEventListener::init(IEventTarget * pTarget)
{
	m_pTarget = pTarget;
	if(m_pTarget && m_pAction) {
		CComBSTR sEvent;
		m_pAction->getEvent(&sEvent);
		m_pTarget->addEventListener(sEvent, this, FALSE);
	}
}

CXFormsXFormsElement * CImplAction::getXFormsElement()
{
	return m_pXForms;
}

HRESULT CImplAction::init(CImpl * pParent)
{

	HRESULT hr = S_OK;

	hr = CImpl::init(pParent);

//	CImpl::m_pXForms = pParent->m_pXForms;

	if(!m_pTarget)
	{
		if(m_sObserver.Length() > 0)
		{
			if(S_OK == GetIdResolver()->getItem(m_sObserver,m_pControl))
			{
				m_pControl->QueryInterface(IID_IEventTarget,(void**)&m_pTarget);
			}
		}
		else if(pParent)
		{
			pParent->QueryInterface(IID_IEventTarget,(void**)&m_pTarget);
		}

		if(!m_pTarget && m_pParentImpl)
		{
			m_pParentImpl->QueryInterface(IID_IEventTarget,(void**)&m_pTarget);
		}

	}
	
	if(m_pTarget)
	{
			//CComObject<CImplActionEventListener> * pEventListener;
			CComObject<CImplActionEventListener>::CreateInstance(&m_pEventListener);
			if(m_pEventListener)
			{
				m_pEventListener->AddRef();
				m_pEventListener->m_pAction = (IAction *) this;
				//This object is not interested in target any more.
				m_pEventListener->init(m_pTarget);		
		//		m_pTarget.Release();

		//			m_pEventListener = pEventListener;
			}
	}
	else
	{
		//TODO: need a better method here.  For now, these elements 
		//	are nothing without an event to fire them, but they may not be in
		//	the future.
		//Return E_FAIL, to allow the caller to delete.
		hr = S_FALSE;
	}
	return hr;
}


HRESULT CImplAction::getEvent(BSTR * sEvent)
{
	*sEvent = m_sEvent.Copy();

	return S_OK;
}


HRESULT CImplAction::PrepareIterationNodeset(unsigned long * lSize)
{
	HRESULT hr;
	if(m_sIterate)
	{
		CComPtr<IXMLDOMNode> pNode;
		hr = GetXPathContext(&pNode);
		CImplModel * pModel = 0;
		if(m_pModel)
			pModel = m_pModel;
		else
			m_pXForms->getModel(0,pModel);

		hr = pModel->getInstanceNodeList(m_sIterate,pNode,false,&m_pContextSet);
		if(m_pContextSet)
			hr = m_pContextSet->get_snapshotLength(lSize);
		else
		{
			hr = S_FALSE;
			*lSize = 0;
		}
	}
	else
	{
		*lSize = 1;
		hr = S_FALSE;
	}
	return hr;
}

HRESULT CImplAction::FlushIterationNodeset()
{
	m_pContextSet.Release();
	return S_OK;
}

